package gimys;

import gimys.Animal.Agressivity;
import gimys.SolidObject.Direction;
import gimys.SolidObject.Type;
import java.util.ArrayList;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;

public class GameplayState extends BasicGameState {

	protected int ID;
	protected int time;
	protected ArrayList<SolidObject> solidObjects;
	protected ArrayList<Deco> lesDeco;
	protected ArrayList<Trigger> triggers;
	protected float panX = 0, panY = 0;
	protected float windowsWidth = GimysGame.displayWidth, windowsHeight = GimysGame.displayHeight;
	protected float mapWidth = 1024, mapHeight = 1536;
	protected Image background, maskCollider, imgJauge, imgBar;
	protected Image seduceImage, getSeducedImage, pause;
	protected SpriteSheet toolBox, imgEplosion;
	protected Player player;
	protected Key key;
	protected Door door;
	protected boolean afficheCollision = true;
	protected boolean afficheAttack = true;
	protected boolean afficheAgro = true;
	protected boolean paused = false;
	boolean nextLevel = false;
	protected int currentLevel;
	
	private Animal target;

	protected AlphaAgro alphaAgro = new AlphaAgro(1.0f);
	
	public GameplayState(int gameplaystate) throws SlickException {
		this.ID = gameplaystate;
	}

	public int getID() {
		return this.ID;
	}

	public void init(GameContainer container, StateBasedGame game) throws SlickException {
		System.out.println("init GameplayState");
		// image loading
		seduceImage     = new Image("data/seduce.png");
		getSeducedImage = new Image("data/getSeduced.png");
		pause           = new Image("data/pause.png");
		imgJauge        = new Image("data/Jauge.png");
		imgBar          = new Image("data/Life_bar_small.png");
		imgEplosion = new SpriteSheet("data/ExplosionPetit.png", 16, 16);
		
		this.currentLevel = GimysGame.startLevel;
		Level.loadLevel(this.currentLevel, this, container);
		this.time = 0;
	}

	public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
		// BackGround
		this.background.draw(0.0f, 0.0f, this.windowsWidth, this.windowsHeight, this.panX, this.panY, this.panX + this.windowsWidth, this.panY + this.windowsHeight);
		// Decorations
		for ( Deco unDeco : lesDeco){
			unDeco.panDraw(panX, panY);
		}
		// boucle sur les solidobject
		for (SolidObject solidObject : solidObjects) {
			Rectangle authorized = new Rectangle(0, 0, this.windowsWidth, this.windowsHeight);
			// si objet dans la fenetre de vision
			if (authorized.contains(solidObject.getLocation().x - panX + solidObject.deltaXSprite, solidObject.getLocation().y - panY + solidObject.deltaYSprite)) {
				solidObject.getImage().draw(solidObject.getLocation().x - panX + solidObject.deltaXSprite, solidObject.getLocation().y - panY + solidObject.deltaYSprite);
				if(afficheCollision || afficheAttack || afficheAgro){
					// pour faire clignoter les cercles d'agro en rouge
					g.setColor( new Color(1.0f,0,0,alphaAgro.glow(time)) );}
				if(afficheCollision){
					ShapeDraw.draw( solidObject.collisionShape, panX, panY );}
				if ( (afficheAttack || afficheAgro) && solidObject.type == Type.ANIMAL){
					if(afficheAttack){
						if (solidObject.getSkin() == player.getSkin()){
							// les amis sont en vert
							g.setColor( new Color(0,1.0f,0,alphaAgro.glow(time)) );
						}
						ShapeDraw.agro( solidObject.collisionShape, (((Animal)solidObject).getRadiusAttack()), panX, panY );
					}
					if(afficheAgro){
						ShapeDraw.agro( solidObject.collisionShape, (((Animal)solidObject).getRadiusDetection()), panX, panY );
					}
				}
			}
		}
		// Gui
		this.renderGUI();
		
	}

	public void renderGUI() throws SlickException {
		if (this.key != null) {
			if (this.key.isCollected) {
				key.getImage().draw(10, 0);
			}
		}
		//for (int i = 0; i < Math.min(2, this.player.getNumberOfSkins()); i++) { inutil de compliquer les choses...
		for (int i = 0; i < this.player.getNumberOfSkins(); i++) {
			player.getGuiSkin(i).draw(15, 50 + 20 * i);
		}
		imgBar.draw(0, 410);
		imgJauge.draw(11, 415 - this.player.getSeductionLevel(), 32, this.player.getSeductionLevel());

	}

	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		if (this.player.died) {
			this.resetLevel(container);
		}
		time = time+delta;

		Input input = container.getInput();
		if (input.isKeyPressed(Input.KEY_PAUSE) || input.isKeyPressed(Input.KEY_P)) {
			this.paused = !this.paused;
			System.out.println("Pause !!!" + this.paused);
			this.pause.draw(0, 450);
		}
		if (input.isKeyPressed(Input.KEY_ESCAPE)) {
			this.resetLevel(container);
		}
		if (!this.paused) {
			time = time+delta;

			if (input.isKeyDown(Input.KEY_LCONTROL)) {

				ArrayList<Animal> closeAnimals = this.getLowDangerAnimals();
				if (!closeAnimals.isEmpty()) {
					Animal closest = closeAnimals.get(0);
					for (Animal a : closeAnimals) {
						if (this.player.getDistance(a) < this.player.getDistance(closest)) {
							closest = a;
						}
					}
					this.target = closest;
					this.target.getSeducedEventHandler(this.player);
					if (this.target.getType() == Type.PRINCE) {
						game.enterState(GimysGame.ENDSTATE);
					}
				}
			}
			if (input.isKeyDown(Input.KEY_LALT)) {
				ArrayList<Animal> closeAnimals = this.getLowDangerAnimals();
				if (!closeAnimals.isEmpty()) {
					Animal closest = closeAnimals.get(0);
					for (Animal a : closeAnimals) {
						if (this.player.getDistance(a) < this.player.getDistance(closest)) {
							closest = a;
						}
					}
					this.target = closest;
					this.target.breakHeartEventHandler(this.player);
				}
			}

			// Update players
			this.updatePlayer(input, delta);
			// Update animals
			ArrayList<SolidObject> toKill = new ArrayList<SolidObject>();
			for (SolidObject object : solidObjects) {
				if (object.getType() == Type.ANIMAL) {
					Animal animal = (Animal)object;
					toKill.addAll(this.updateAnimal(delta, animal));
				}
			}
			// Cleaning
			for (SolidObject object : toKill) {
				this.kill(object);
			}

			this.updatePan();
			this.updateTriggers();

			// fin du niveau?
			if(nextLevel){
				currentLevel++;
				nextLevel = false;
				Level.loadLevel(currentLevel, this, container);
				this.time = 0;
			}
			// restart du niveau
			if (this.player.died) {
				this.resetLevel(container);
			}

		}
	}


	private void updatePan() {
		this.panX = Math.max(0, this.player.getLocation().x - (this.windowsWidth / 2));
		this.panY = Math.max(0, this.player.getLocation().y - (this.windowsHeight / 2));
		this.panX = Math.min(this.panX, this.mapWidth - this.windowsWidth);
		this.panY = Math.min(this.panY, this.mapHeight - this.windowsHeight);
	}


	private void updatePlayer(Input input, int delta) {
		this.player.update(delta);
		Point oldLocation = this.player.getLocation();
		Point newLocation = new Point(oldLocation.x, oldLocation.y);
		float dx=0, dy=0;

		this.player.hasMoved = false;
		// Get new position
		if(input.isKeyDown(Input.KEY_LEFT))	{
			dx += -this.player.getSpeed() * delta;
			dy += 0;
			this.player.setDirection(Direction.LEFT);
		}
		if(input.isKeyDown(Input.KEY_RIGHT)) {
			dx += this.player.getSpeed() * delta;
			dy += 0;
			this.player.setDirection(Direction.RIGHT);
		}
		if(input.isKeyDown(Input.KEY_UP)) {
			dx += 0;
			dy += -this.player.getSpeed() * delta;
			this.player.setDirection(Direction.TOP);
		}
		if(input.isKeyDown(Input.KEY_DOWN)) {
			dx += 0;
			dy += this.player.getSpeed() * delta;
			this.player.setDirection(Direction.BOTTOM);
		}
		// si diagonal
		if(dx != 0f && dy != 0f ){
			dx = (float)(dx / Math.sqrt(2));
			dy = (float)(dy / Math.sqrt(2));
		}
		// si on a bougé
		if (dx != 0f || dy != 0f) {
			this.player.hasMoved = true;
			newLocation.x = newLocation.x + dx;
			newLocation.y = newLocation.y + dy;
		}
		if (newLocation.x < 0) {
			newLocation.x = 0;
		}
		if (newLocation.x > this.mapWidth) {
			newLocation.x = this.mapWidth;
		}
		if (newLocation.y > this.mapHeight) {
			newLocation.y = this.mapHeight;
		}
		if (newLocation.y < 0) {
			newLocation.y = 0;
		}

		this.player.setLocation(newLocation);

		// Verify whether the player is in detection or attack zones of the animals
		ArrayList<Animal> lowDangerAnimals = this.getLowDangerAnimals();
		for (Animal animal : lowDangerAnimals) {
			animal.setTarget(player);
		}

		// Verify whether the player is in attack zones of the animals
		ArrayList<Animal> highDangerAnimals = this.getHighDangerAnimals();
		for (Animal animal : highDangerAnimals) {
			animal.setTarget(player);
		}

		// Update speed for animals
		for (SolidObject o : this.solidObjects) {
			if (o.getType() == Type.ANIMAL) {
				if (highDangerAnimals.contains(o)) {
					if(o.getSkin() == this.player.getSkin() ){
						// Un amis recule de la zone d'attaque
						o.setSpeed(-0.02f);
						//System.out.println("attack zonne friend");
					}else{
						// Attack zone
						o.setSpeed(0.12f);
					}
				}
				else {
					if(o.getSkin() == this.player.getSkin() ){
						// Un amis suit plus rapidement
						o.setSpeed(0.09f);
						//System.out.println("attack zonne friend");
					}else{
						// detection zone
						o.setSpeed(0.07f);
					}
				}
			}
		}

		// Verify collision
		boolean collision = this.collidesToMask(this.player);
		ArrayList<SolidObject> colliders = this.collidesToObjects(this.player);
		if (colliders.size() > 0) collision = true;
		if (collision) {
			// If collision with mask or object, go back
			this.player.setLocation(oldLocation);
			// If collision between player and animal
			if (colliders.size() > 0) {
				for (SolidObject collider : colliders) {
					if (collider.getType() == Type.ANIMAL) {
						Animal animal = (Animal)collider;
						this.player.collisionHandler(animal);
					} else if(collider.getType() == Type.KEY){
						// je prend la clef
						key.isCollected = true;
						// elle s'affiche plus
						this.solidObjects.remove(key);
					}else if(collider.getType() == Type.DOOR){
						// door => si on a la clef on sort du level
						if (key.isCollected){
							nextLevel = true;
						}
					}
				}
			}
		}
	}

	private ArrayList<SolidObject> updateAnimal(int delta, Animal animal) {
		ArrayList<SolidObject> toKill = new ArrayList<SolidObject>();
		animal.update(delta);

		// Define goal
		Point goal;
		if (animal.getTarget() != null) {
			goal = animal.getTarget().getLocation();
			// Verify if the target is still up to date
			if (animal.getDistance(animal.getTarget()) > animal.getRadiusDetection()) {
				animal.setTarget(null);
				goal = animal.getPath().getPoint();
			}
		}
		else {
			goal = animal.getPath().getPoint();
		}
		toKill.addAll(this.moveAnimal(animal, goal , delta));
		return toKill;
	}

	private ArrayList<SolidObject> moveAnimal(Animal animal, Point goal, int delta) {
		ArrayList<SolidObject> toKill = new ArrayList<SolidObject>();
		Point oldLocation = animal.getLocation();
		Point newLocation = new Point(oldLocation.x, oldLocation.y);

		animal.hasMoved = false;

		// Calculate new location
		float bigD = (float)Math.sqrt( Math.pow(goal.x-animal.getLocation().x,2) + Math.pow(goal.y-animal.getLocation().y,2) );
		float distanceToDo = delta * animal.getSpeed();
		if (distanceToDo > bigD) {
			newLocation = goal;
			animal.getPath().nextPoint();
		}
		else {
			newLocation.x += (float) (( goal.x - animal.getLocation().x )*distanceToDo/bigD) ;
			newLocation.y += (float) (( goal.y - animal.getLocation().y )*distanceToDo/bigD) ;
		}
		animal.setLocation(newLocation);

		// Verify collision
		boolean collision = this.collidesToMask(animal);
		ArrayList<SolidObject> colliders = this.collidesToObjects(animal);
		if (colliders.size() > 0) collision = true;
		if (animal.getAttackTimer() < 3000 || collision) {
			// If animal just eat or collide with mask or object, don't move
			animal.setLocation(oldLocation);
		}
		// si collision avec un object
		if (colliders.size() > 0){
			// If collision between player and animal
			if ( colliders.contains(this.player)) {
				this.player.collisionHandler(animal);
			}else{
				// si collision avec un ennemis
				for ( SolidObject colSolid : colliders ) {
					if( colSolid.type == Type.ANIMAL &&
							(  animal.getAgressivityToward(colSolid.getSkin()) == Agressivity.VERY_AGRESSIVE
									|| animal.getAgressivityToward(colSolid.getSkin()) == Agressivity.LITTLE_AGRESSIVE) ){

						// double kill
						toKill.add(animal);
						toKill.add(colSolid);
						System.out.println("double kill");
					}
				}
			}
		}
		if (animal.getLocation().x != oldLocation.x || animal.getLocation().y != oldLocation.y) {
			animal.hasMoved = true;
		}
		return toKill;
	}


	/***** Collision handlers *****/
/*
	private void playerCollisionHandler(Animal animal) {
		if (animal.getSkin() != this.player.getSkin()) {
			System.out.println("Player touched !");
			// todo : mettre l'animal en timeout
			if (this.player.destroyCurrentSkin()) {
				this.player.died = true;
			}
		}
	}
*/

	private void resetLevel(GameContainer container) throws SlickException {
		Level.loadLevel(this.currentLevel, this, container);
		this.time = 0;
		this.player.died = false;
	}

	/***** Methods for collision testing *****/

	private boolean collidesToMask(SolidObject object) {
		// Collision with the mask
		int r = (int)object.getCollisionShape().getBoundingCircleRadius();
		int xCenter = (int)object.getCollisionShape().getCenterX();
		int yCenter = (int)object.getCollisionShape().getCenterY();
		int x = (int)object.getCollisionShape().getX();
		int y = (int)object.getCollisionShape().getY();
		int h = (int)object.getCollisionShape().getHeight();
		int w = (int)object.getCollisionShape().getWidth();
		for (int i=x ; i<x+w ; i++) {
			for (int j=y ; j<y+h ; j++) {
				// If pixel is in circle
				if (Math.pow(xCenter-i,2)+Math.pow(yCenter-j,2) < r*r) {
					Color c = this.maskCollider.getColor(i,j);
					if (c.equals(Color.black)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public ArrayList<SolidObject> collidesToObjects(SolidObject object) {
		// Collision with other solid objects
		ArrayList<SolidObject> colliders = new ArrayList<SolidObject>();
		for (SolidObject collider : this.solidObjects) {
			if (!collider.equals(object)) {
				if (object.getCollisionShape().intersects(collider.getCollisionShape())) {
					colliders.add(collider);
				}
			}
		}
		return colliders;
	}

	public ArrayList<SolidObject> getDetectedObjects(Animal animal) {
		ArrayList<SolidObject> detectedObjects = new ArrayList<SolidObject>();
		for (SolidObject object : solidObjects) {
			if (animal.getDistance(object) < animal.getRadiusDetection()) {
				detectedObjects.add(object);
			}
		}
		return detectedObjects;
	}

	public ArrayList<Animal> getLowDangerAnimals() {
		ArrayList<Animal> lowDangerAnimals = new ArrayList<Animal>();
		for (SolidObject object : solidObjects) {
			if (object.getType() == Type.ANIMAL || object.getType() == Type.PRINCE) {
				Animal animal = (Animal)object; 
				if (animal.getDistance(this.player) < animal.getRadiusDetection()
				/*&& animal.getSkin() != this.player.getSkin()*/) {
					lowDangerAnimals.add(animal);
				}
			}
		}
		return lowDangerAnimals;
	}

	public ArrayList<Animal> getHighDangerAnimals() {
		ArrayList<Animal> highDangerAnimals = new ArrayList<Animal>();
		for (SolidObject object : solidObjects) {
			if (object.getType() == Type.ANIMAL) {
				Animal animal = (Animal)object; 
				if (animal.getDistance(this.player) < animal.getRadiusAttack()
				/*&& animal.getSkin() != this.player.getSkin()*/ ) {
					highDangerAnimals.add(animal);
				}
			}
		}
		return highDangerAnimals;
	}

	public void kill(SolidObject object) {
		if (object.type == Type.ANIMAL) {
			// explosion
			Deco explosion = new Deco(imgEplosion, 100, object.getLocation());
			lesDeco.add(explosion);
			
			this.solidObjects.remove(object);
		}
		if (object.type == Type.PLAYER) {
			((Player)object).destroyCurrentSkin();
		}
	}

	public void updateTriggers() throws SlickException {
		for (Trigger t : this.triggers) {
			switch (t.type) {
			case PATROL :
				if ((time % t.freq) == 0) {
					solidObjects.add(new Animal(t.location, t.skin, new Path(t.pathUsed)));
				}
				break;
			case EMBUSH :
				if (player.getLocation().getDistance(t.location) < 10) {
					for (int i = 0; i < t.number; i++) {
						solidObjects.add(new Animal(t.embushPoint, t.skin, t.pathUsed));
					}
					t.number = 0;
				}
				break;
			}
		}
	}
}